package com.rrjjg.app.core.builders;

import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ----------------------------------------------------------------------------- <br>
 * 功能 :  <br>
 * ----------------------------------------------------------------------------- <br>
 * 作者 : Vayne <br>
 * 时间 : 2022/9/16 9:30 <br>
 */
@Slf4j
@Data
@Accessors(chain = true)
public class ThreadPoolBuilder {

    private int corePoolSize;
    private int maximumPoolSize;
    private int keepAliveTime;
    private int queueCapacity;
    private AsyncThreadFactory threadFactory;
    private RejectedExecutionHandler handler;
    private final static ConcurrentHashMap<String, ThreadPoolExecutor> cache = new ConcurrentHashMap<>();
    private final static ScheduledThreadPoolExecutor scheduled = new ScheduledThreadPoolExecutor(4);


    public ThreadPoolExecutor build() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        threadPoolTaskExecutor.setMaxPoolSize(maximumPoolSize);
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveTime);
        threadPoolTaskExecutor.setThreadFactory(threadFactory);
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        threadPoolTaskExecutor.setRejectedExecutionHandler(handler == null ? new ThreadPoolExecutor.AbortPolicy() : handler);
        threadPoolTaskExecutor.setTaskDecorator(new MdcTaskDecorator());
        threadPoolTaskExecutor.initialize();
        ThreadPoolExecutor threadPoolExecutor = threadPoolTaskExecutor.getThreadPoolExecutor();
        scheduled.scheduleAtFixedRate(new ThreadPoolMonitor(threadPoolExecutor), 60, 60, TimeUnit.SECONDS);
        cache.put(threadFactory.getNamePrefix(), threadPoolExecutor);
        return threadPoolExecutor;
    }


    /**
     * 功能 ： 打印线程池资源
     * 作者 ： Vayne
     */
    public static void printCache() {
        if (!cache.isEmpty()) {
            cache.forEach((k, v) -> {
                int activeCount = v.getActiveCount();
                int poolSize = v.getPoolSize();
                int largestPoolSize = v.getLargestPoolSize();
                long completedTaskCount = v.getCompletedTaskCount();
                int size = v.getQueue().size();
                log.info("thread monitor check name={} ,poolSize={} " +
                        ",largestPoolSize={} ,activeCount={} ,completedTaskCount={} ,queueSize={}", k, poolSize, largestPoolSize, activeCount, completedTaskCount, size);

            });
        }
    }

    /**
     * 功能 ： 打印线程池资源
     * 作者 ： Vayne
     */
    public static void printCacheByName(String name) {
        if (!cache.isEmpty()) {
            ThreadPoolExecutor executor = cache.get(name);
            if (executor != null) {
                int activeCount = executor.getActiveCount();
                int poolSize = executor.getPoolSize();
                int largestPoolSize = executor.getLargestPoolSize();
                long completedTaskCount = executor.getCompletedTaskCount();
                int size = executor.getQueue().size();
                log.info("thread monitor check name={} ,poolSize={}  " +
                        ",largestPoolSize={} ,activeCount={} ,completedTaskCount={} ,queueSize={}", name, poolSize, largestPoolSize, activeCount, completedTaskCount, size);
            }
        }
    }

    static class MdcTaskDecorator implements TaskDecorator {

        @Override
        public Runnable decorate(Runnable runnable) {
            final Map<String, String> map = MDC.getCopyOfContextMap();
            return () -> {
                try {
                    if (map != null) {
                        MDC.setContextMap(map);
                    }
                    runnable.run();
                } finally {
                    MDC.clear();
                }
            };
        }
    }

    @Slf4j
    static class ThreadPoolMonitor implements Runnable {

        private ThreadPoolExecutor threadPoolExecutor;

        public ThreadPoolMonitor(ThreadPoolExecutor threadPoolExecutor) {
            this.threadPoolExecutor = threadPoolExecutor;
        }

        @Override
        public void run() {
            try {
                String thradPoolName = ((AsyncThreadFactory) threadPoolExecutor.getThreadFactory()).getNamePrefix();
                int activeCount = threadPoolExecutor.getActiveCount();
                int poolSize = threadPoolExecutor.getPoolSize();
                int largestPoolSize = threadPoolExecutor.getLargestPoolSize();
                long completedTaskCount = threadPoolExecutor.getCompletedTaskCount();
                int size = threadPoolExecutor.getQueue().size();
                if(log.isDebugEnabled()){
                    log.debug("thread monitor name={} ,poolSize={}" +
                            ",largestPoolSize={} ,activeCount={} ,completedTaskCount={} ,queueSize={}", thradPoolName, poolSize, largestPoolSize, activeCount, completedTaskCount, size);
                }
            } catch (Exception e) {
                log.error("ThreadPoolMonitor is error ", e);
            }
        }
    }

    public static class AsyncThreadFactory implements ThreadFactory {
        private final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        public AsyncThreadFactory(String namePrefix) {
            group =  Thread.currentThread().getThreadGroup();
            this.namePrefix = namePrefix + poolNumber.getAndIncrement() + "-thread-";
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
            if (t.isDaemon()) t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }

        public String getNamePrefix() {
            return namePrefix;
        }
    }


}


